.. _Классы:

Классы
=======

.. index::
    class
    structure

Подобно структурам, класс - это функция для определения новых типов. Согласно этому определению,
классы - это типы, определяемые пользователем. В отличие от структур, классы обеспечивают объектно
парадигму объектно-ориентированного программирования (ООП) в D. Основными аспектами ООП являются следующие
следующие:
∙ Инкапсуляция: Контроль доступа к членам (инкапсуляция доступна и для
структур, но об этом не говорилось до этой главы).
∙ Наследование: Приобретение членов другого типа
∙ Полиморфизм: Возможность использовать более специальный тип вместо более
общий тип
Инкапсуляция достигается с помощью атрибутов защиты, которые мы рассмотрим в следующей главе (:ref:`Инкапсуляция и защита атрибутов<глава_60_start>`).
Наследование служит для приобретения реализаций других типов.
Полиморфизм (:ref:`Глава Наследование<глава_55_start>`) служит для абстрагирования частей программ друг от друга
и достигается с помощью интерфейсов классов.
В этой главе мы познакомимся с классами на высоком уровне, подчеркнув тот факт, что они
являются ссылочными типами. Более подробно классы будут рассмотрены в последующих главах.

Сравнение со структурами
------------------------

В целом, классы очень похожи на структуры. Большинство функций, которые мы
рассматривались в следующих главах, применимы и к классам:
* :ref:`Структуры<Структуры>`
* :ref:`Функции члены<Функции члены>`
* :ref:`Параметры const ref и константные функции-члены<Параметры const ref и константные функции-члены>`
* :ref:`Конструктор и другие специальные функции<Конструктор и другие специальные функции>`
* :ref:`Перегрузка операторов<глава_53_start>`
Однако между классами и структурами есть важные различия.

**Классы являются ссылочными типами**

Самое большое отличие от структур заключается в том, что структуры - это типы значений, а классы - это
ссылочные типы. Остальные различия, описанные ниже, в основном связаны с этим фактом.

**Переменные класса могут быть null**

Как уже было кратко сказано в главе (:ref:`NULL значение и оператор is<глава_45_start>`), переменные класса могут быть нулевыми. Другими словами, переменные класса могут не
предоставлять доступ к какому-либо объекту. Переменные класса сами по себе не имеют значений;
Фактические объекты класса должны быть созданы с помощью ключевого слова new.
Как вы помните, сравнение ссылки с null с помощью оператора == или != является ошибкой. Вместо этого сравнение должно выполняться с помощью оператора is или
!is, соответственно:

.. code-block:: d

    MyClass referencesAnObject = new MyClass;
    assert(referencesAnObject !is null);
    MyClass variable; // does not reference an object
    assert(variable is null);

Причина в том, что оператору == может понадобиться обратиться к значениям членов
объектов, и попытка доступа к ним через потенциально
null переменную приведет к ошибке доступа к памяти. По этой причине переменные класса
всегда должны сравниваться операторами is и !is

**Переменные класса в сравнении с объектами класса**

Переменная класса и объект класса - это разные понятия.
Объекты класса создаются с помощью ключевого слова new; у них нет имен. На сайте
Фактическая концепция, которую тип класса представляет в программе, обеспечивается объектом класса
объект. Например, если предположить, что класс Student представляет студентов по их
именами и оценками, то такая информация будет храниться в членах объекта Student
объектов. Отчасти потому, что они анонимны, невозможно получить доступ к объектам класса
объектам напрямую.
С другой стороны, переменная класса - это функция языка для доступа к объектам класса
объектам. Хотя синтаксически может показаться, что операции выполняются
над переменной класса, на самом деле эти операции передаются объекту класса.
Рассмотрим следующий код, который мы видели ранее в главе Типы значений и
ссылочные типы (:ref:`Типы значения и ссылочные типы<глава_34_start>`):

.. code-block:: d

    auto variable1 = new MyClass;
    auto variable2 = variable1;

Ключевое слово new создает анонимный объект класса. variable1 и
variable2 выше просто предоставляют доступ к этому анонимному объекту:

.. image:: images/54.1_1.png

**Копирование**

Копирование затрагивает только переменные, но не объект.
Поскольку классы являются ссылочными типами, определение новой переменной класса как копии
другой, создаются две переменные, предоставляющие доступ к одному и тому же объекту. Сам
объект не копируется.
Функция postblit this(this) недоступна для классов.

.. code-block:: d

    auto variable2 = variable1;

В приведенном выше коде переменная variable2 инициализируется переменной variable1. Обе переменные
начинают предоставлять доступ к одному и тому же объекту.
Когда необходимо скопировать реальный объект, класс должен иметь функцию-член
функция для этой цели. Чтобы быть совместимой с массивами, эта функция может быть
dup(). Эта функция должна создавать и возвращать новый объект класса. Давайте посмотрим
это на классе, который имеет различные типы членов:


.. code-block:: d

    class Foo
    {
        S o; // assume S is a struct type
        char[] s;
        int i;
        // ...
        this(S o, const char[] s, int i)
        {
            this.o = o;
            this.s = s.dup;
            this.i = i;
        }

        Foo dup() const
        {
            return new Foo(o, s, i);
        }
    }


Функция-член dup() создает новый объект, используя возможности конструктора
конструктора Foo и возвращает новый объект. Обратите внимание, что конструктор копирует
член s явно с помощью свойства .dup массивов. Будучи типами значений, o и i
копируются автоматически.
Следующий код использует dup() для создания нового объекта:

.. code-block:: d

    auto var1 = new Foo(S(1.5), "hello", 42);
    auto var2 = var1.dup();

В результате объекты, связанные с var1 и var2, будут разными.
Аналогично, неизменяемая копия объекта может быть предоставлена функцией-членом
функции с соответствующим названием idup(). В этом случае конструктор должен быть
также должен быть определен как чистый. Мы рассмотрим ключевое слово pure в одной из следующих глав (:ref:`Дополнительно о функциях<Дополнительно о функциях>`).

.. code-block:: d

    class Foo
    {
        // ...
        this(S o, const char[] s, int i) pure
        {
            // ...
        }

        immutable(Foo) idup() const
        {
            return new immutable(Foo)(o, s, i);
        }
    }
    // ...
    immutable(Foo) imm = var1.idup();


**Присвоение**

Как и копирование, присваивание влияет только на переменные.
Присвоение переменной класса отсоединяет эту переменную от ее текущего объекта
и связывает ее с новым объектом.
Если не существует другой переменной класса, которая все еще предоставляет доступ к объекту, от которого она была
от которого она была отвязана, то этот объект будет уничтожен некоторое время в
в будущем сборщиком мусора.

.. code-block:: d

    auto variable1 = new MyClass();
    auto variable2 = new MyClass();
    variable1 = variable2;

Приведенное выше присваивание заставляет переменную variable1 покинуть свой объект и начать предоставлять
доступ к объекту посредством переменной variable2. Поскольку для исходного объекта не существует другой переменной адресующей исходный объект, этот объект будет уничтожен сборщиком мусора.
Поведение присваивания не может быть изменено для классов. Другими словами,
opAssign не может быть перегружен для них.

**Определение**

Классы определяются ключевым словом class вместо ключевого слова struct:

.. code-block:: d

**Конструктор**

Как и в случае со структурами, имя конструктора - this. В отличие от структур, объекты классов
не могут быть сконструированы с помощью синтаксиса { }.

.. code-block:: d

    class ChessPiece
    {
        dchar shape;
        this(dchar shape)
        {
            this.shape = shape;
        }
    }

В отличие от структур, здесь нет автоматического построения объекта, когда параметры конструктора
параметры последовательно присваиваются членам:

.. code-block:: d

    class ChessPiece
    {
        dchar shape;
        size_t value;
    }

    void main()
    {
        auto king = new ChessPiece('k', 100); //  ошибка компиляции
    }

.. code-block:: console

    Error: no constructor for ChessPiece

Чтобы этот синтаксис работал, конструктор должен быть явно определен
программистом.

**Деструктор**

Как и в случае со структурами, имя деструктора - ~this

.. code-block:: d

    ~this()
    {
    // ...
    }

Однако, в отличие от структур, деструкторы классов не выполняются в тот момент.
когда заканчивается время жизни объекта класса. Как мы видели выше, деструктор выполняется
выполняется некоторое время в будущем во время цикла сборки мусора. (По этому
различие, деструкторы классов правильнее было бы называть финализаторами).
Как мы увидим далее в главе "Управление памятью" (стр. 667), деструкторы классов должны соблюдать следующие правила
деструкторы должны соблюдать следующие правила:
* Деструктор класса не должен обращаться к члену, который управляется сборщиком мусора.
сборщиком мусора. Это связано с тем, что сборщики мусора не обязаны гарантировать, что
объект и его члены завершаются в каком-либо определенном порядке. Все члены
могут быть уже завершены на момент выполнения деструктора.
* Деструктор класса не должен выделять новую память, которой управляет
сборщиком мусора. Это связано с тем, что сборщики мусора не обязаны
гарантировать, что они смогут выделять новые объекты во время цикла сборки мусора.
Нарушение этих правил повлечет за собой неопределенное поведение.
пример такой проблемы, просто попытавшись выделить объект в деструкторе класса:

.. code-block:: d

    class C
    {
        ~this()
        {
            auto c = new C(); // ← WRONG: Allocates explicitly
                              //          in a class destructor
        }
    }

    void main()
    {
        auto c = new C();
    }

Программа завершается с исключением:

.. code-block:: console

    core.exception.InvalidMemoryOperationError@(0)

Столь же неправильно выделять новую память косвенно от сборщика мусора
в деструкторе. Например, память, используемая для элементов динамического массива, выделяется
также управляется сборщиком мусора. Использование массива таким образом, что
требует выделения нового блока памяти для элементов, также является неопределенным поведением.
также:

.. code-block:: d

    ~this()
    {
        auto arr = [ 1 ]; // ← WRONG: Allocates indirectly
                          //   in a class destructo
    }

**Доступ к членам**

Как и в случае со структурами, доступ к членам осуществляется с помощью оператора dot:

.. code-block:: d

    auto king = new ChessPiece('♔');
    writeln(king.shape);

Хотя по синтаксису кажется, что доступ осуществляется к члену переменной
на самом деле это член объекта. Переменные класса не имеют
членов, это делают объекты класса. У переменной king нет члена shape,
а у анонимного объекта есть.
*Примечание: Обычно не следует обращаться к членам напрямую, как в приведенном выше коде. Когда
когда требуется именно такой синтаксис, следует использовать свойства, о которых будет рассказано в
последующей главе* (:ref:`Свойства<Свойства>`).

**Перегрузка операторов**

За исключением того, что opAssign не может быть перегружен для классов, перегрузка операторов
перегрузка операторов такая же, как и для структур. Для классов значение оператора opAssign всегда
ассоциирование переменной класса с объектом класса.

**Функции-члены**

Хотя функции-члены определяются и используются так же, как и структуры, существует
есть одно важное отличие: Функции-члены класса могут быть и по умолчанию являются
переопределяемыми. Мы рассмотрим эту концепцию позже в главе "Наследование" (стр. 328).
Поскольку переопределяемые функции-члены несут затраты на производительность во время выполнения,
не вдаваясь в подробности, я рекомендую определять все функции класса, которые не нужно
которые не нужно переопределять, с помощью ключевого слова final. Вы можете применять это правило
вслепую, если нет ошибок компиляции:

.. code-block:: d

    class C
    {
        final int func() // ← рекомендуется
        {
        // ...
        }
    }

Еще одно отличие от структур заключается в том, что некоторые функции-члены автоматически
наследуются от класса Object. В следующей главе (стр. 328) мы увидим, как
определение toString может быть изменено с помощью ключевого слова override.

**Операторы is и !is**

Эти операторы работают с переменными класса.
is определяет, предоставляют ли две переменные класса доступ к одному и тому же объекту класса.
Он возвращает true, если объект один и тот же, и false в противном случае. Оператор !is является противоположным
оператору is.

.. code-block:: d

    auto myKing = new ChessPiece('к');
    auto yourKing = new ChessPiece('к');
    assert(myKing !is yourKing);

Поскольку объекты переменных myKing и yourKing различны, оператор !is
возвращает истину. Даже если эти два объекта построены по одному и тому же
символом 'к', они все равно являются двумя разными объектами.
Когда переменные предоставляют доступ к одному и тому же объекту, оператор is возвращает true:

.. code-block:: d

    auto myKing2 = myKing;
    assert(myKing2 is myKing);

Обе приведенные выше переменные предоставляют доступ к одному и тому же объекту.

Резюме
-----------

* Классы и структуры обладают общими чертами, но имеют существенные различия.
* Классы являются ссылочными типами. Ключевое слово new создает анонимный класс
  и возвращает переменную класса.
* Переменные класса, не связанные ни с одним объектом, равны нулю. Проверка
  нуля должна осуществляться с помощью is или !is, а не == или !=.
* При копировании с объектом связывается дополнительная переменная. Для того чтобы
  копировать объекты класса, тип должен иметь специальную функцию dup().
* Присвоение ассоциирует переменную с объектом. Это поведение не может быть изменено.


